/**
 * @license 
 * jQuery Tools Validator @VERSION - HTML5 is here. Now use it.
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/form/validator/
 * 
 * Since: Mar 2010
 * Date: @DATE 
 */
/*jslint evil: true */ 
(function($) {	

    $.tools = $.tools || {
        version: '@VERSION'
    };
		
    // globals
    var typeRe = /\[type=([a-z]+)\]/, 
    numRe = /^-?[0-9]*(\.[0-9]+)?$/,
    dateInput = $.tools.dateinput,
		
    // http://net.tutsplus.com/tutorials/other/8-regular-expressions-you-should-know/
    emailRe = /^([a-z0-9_\.\-\+]+)@([\da-z\.\-]+)\.([a-z\.]{2,6})$/i,
    urlRe = /^(https?:\/\/)?[\da-z\.\-]+\.[a-z\.]{2,6}[#&+_\?\/\w \.\-=]*$/i,
    v;
		 
    v = $.tools.validator = {
		
        conf: {   
            grouped: false, 				// show all error messages at once inside the container 
            effect: 'default',			// show/hide effect for error message. only 'default' is built-in
            errorClass: 'invalid',		// input field class name in case of validation error		
			
            // when to check for validity?
            inputEvent: null,				// change, blur, keyup, null 
            errorInputEvent: 'keyup',  // change, blur, keyup, null
            formEvent: 'submit',       // submit, null

            lang: 'en',						// default language for error messages 
            message: '<div/>',
            messageAttr: 'data-message', // name of the attribute for overridden error message
            messageClass: 'error',		// error message element's class name
            offset: [0, 0], 
            position: 'center right',
            singleError: false, 			// validate all inputs at once
            speed: 'normal'				// message's fade-in speed			
        },


        /* The Error Messages */
        messages: {
            "*": {
                en: "Please correct this value"
            }		
        },
		
        localize: function(lang, messages) { 
            $.each(messages, function(key, msg)  {
                v.messages[key] = v.messages[key] || {};
                v.messages[key][lang] = msg;		
            });
        },
		
        localizeFn: function(key, messages) {
            v.messages[key] = v.messages[key] || {};
            $.extend(v.messages[key], messages);
        },
		
        /** 
		 * Adds a new validator 
		 */
        fn: function(matcher, msg, fn) {
			
            // no message supplied
            if ($.isFunction(msg)) { 
                fn = msg; 
				
            // message(s) on second argument
            } else {
                if (typeof msg == 'string') {
                    msg = {
                        en: msg
                    };  
            }
            this.messages[matcher.key || matcher] = msg;
        }

        // check for "[type=xxx]" (not supported by jQuery)
        var test = typeRe.exec(matcher);                                    
        if (test) {
            matcher = isType(test[1]);
        }				
			
        // add validator to the arsenal
        fns.push([matcher, fn]);		 
    },

    /* Add new show/hide effect */
    addEffect: function(name, showFn, closeFn) {
        effects[name] = [showFn, closeFn];
    }
		
};
	
/* calculate error message position relative to the input */  	
function getPosition(trigger, el, conf) {	
		
    // get origin top/left position 
    var top = trigger.offset().top, 
    left = trigger.offset().left,	 
    pos = conf.position.split(/,?\s+/),
    y = pos[0],
    x = pos[1];
		
    top  -= el.outerHeight() - conf.offset[0];
    left += trigger.outerWidth() + conf.offset[1];
		
		
    // iPad position fix
    if (/iPad/i.test(navigator.userAgent)) {
        top -= $(window).scrollTop();
    }
		
    // adjust Y		
    var height = el.outerHeight() + trigger.outerHeight();
    if (y == 'center') 	{
        top += height / 2;
    }
    if (y == 'bottom') 	{
        top += height;
    }
		
    // adjust X
    var width = trigger.outerWidth();
    if (x == 'center') 	{
        left -= (width  + el.outerWidth()) / 2;
    }
    if (x == 'left')  	{
        left -= width;
    }	 
		
    return {
        top: top, 
        left: left
    };
}	
	

	
// $.is("[type=xxx]") or $.filter("[type=xxx]") not working in jQuery 1.3.2 or 1.4.2
function isType(type) { 
    function fn() {
        return this.getAttribute("type") == type;  	
    } 
    fn.key = "[type=" + type + "]";
    return fn;
}	

	
var fns = [], effects = {
		
    'default' : [
			
    // show errors function
    function(errs) {
				
        var conf = this.getConf();
				
        // loop errors
        $.each(errs, function(i, err) {
						
            // add error class	
            var input = err.input;					
            input.addClass(conf.errorClass);
					
            // get handle to the error container
            var msg = input.data("msg.el"); 
					
            // create it if not present
            if (!msg) { 
                msg = $(conf.message).addClass(conf.messageClass).appendTo(document.body);
                input.data("msg.el", msg);
            }  
					
            // clear the container 
            msg.css({
                visibility: 'hidden'
            }).find("p").remove();
            
            // populate messages
            $.each(err.messages, function(i, m) { 
                $("<p/>").html(m).appendTo(msg);			
            });
					
            // make sure the width is not full body width so it can be positioned correctly
            if (msg.outerWidth() == msg.parent().width()) {
                msg.add(msg.find("p")).css({
                    display: 'inline'
                });		
            } 
					
            // insert into correct position (relative to the field)
            if(input.attr("type") == "checkbox" || input.attr("type") == "radio"){
                var pos = getPosition(input.filter(":last").closest("label"), msg, conf); 
            }else{
                var pos = getPosition(input, msg, conf); 
            }
					 
            msg.css({
                visibility: 'visible', 
                position: 'absolute', 
                top: pos.top, 
                left: pos.left
            })
            .fadeIn(conf.speed);     
        });
						
				
    // hide errors function
    }, function(inputs) {

        var conf = this.getConf();				
        inputs.removeClass(conf.errorClass).each(function() {
            var msg = $(this).data("msg.el");
            if (msg) {
                msg.css({
                    visibility: 'hidden'
                });
            }
        });
    }
    ]  
};

	
/* sperial selectors */
$.each("email,url,number".split(","), function(i, key) {
    $.expr[':'][key] = function(el) {
        return el.getAttribute("type") === key;
    };
});
	

/* 
		oninvalid() jQuery plugin. 
		Usage: $("input:eq(2)").oninvalid(function() { ... });
	*/
$.fn.oninvalid = function( fn ){
    return this[fn ? "bind" : "trigger"]("OI", fn);
};
	
	
/******* built-in HTML5 standard validators *********/
	
v.fn(":email", "Please enter a valid email address", function(el, v) {
    return !v || emailRe.test(v);
});
	
v.fn(":url", "Please enter a valid URL", function(el, v) {
    return !v || urlRe.test(v);
});
	
v.fn(":number", "Please enter a numeric value.", function(el, v) {
    return numRe.test(v);			
});
	
v.fn("[max]", "Please enter a value no larger than $1", function(el, v) {
			
    // skip empty values and dateinputs
    if (v === '' || dateInput && el.is(":date")) {
        return true;
    }	
		
    var max = el.attr("max");
    return parseFloat(v) <= parseFloat(max) ? true : [max];
});
	
v.fn("[min]", "Please enter a value of at least $1", function(el, v) {

    // skip empty values and dateinputs
    if (v === '' || dateInput && el.is(":date")) {
        return true;
    }

    var min = el.attr("min");
    return parseFloat(v) >= parseFloat(min) ? true : [min];
});
	
v.fn("[required]", "Please complete this mandatory field.", function(el, v) {
    if (el.is(":checkbox")) {
        return el.is(":checked");
    }
    return !!v; 			
});
	
v.fn("[pattern]", function(el) {
    var p = new RegExp("^" + el.attr("pattern") + "$");  
    return p.test(el.val()); 			
});

	
function Validator(inputs, form, conf) {		
		
    // private variables
    var self = this, 
    fire = form.add(self);

    // make sure there are input fields available
    inputs = inputs.not(":button, :image, :reset, :submit");			 
		
    // Prevent default Firefox validation
    form.attr("novalidate", "novalidate");

    // utility function
    function pushMessage(to, matcher, returnValue) {
			
        // only one message allowed
        if (!conf.grouped && to.length) {
            return;
        }
			
        // the error message
        var msg;
			
        // substitutions are returned
        if (returnValue === false || $.isArray(returnValue)) {
            msg = v.messages[matcher.key || matcher] || v.messages["*"];
            msg = msg[conf.lang] || v.messages["*"].en;

            // substitution
            var matches = msg.match(/\$\d/g);
				
            if (matches && $.isArray(returnValue)) {
                $.each(matches, function(i) {
                    msg = msg.replace(this, returnValue[i]);
                });
            } 					 
				
        // error message is returned directly
        } else {
            msg = returnValue[conf.lang] || returnValue;
        }
			
        to.push(msg);
    }
		
		
    // API methods  
    $.extend(self, {

        getConf: function() {
            return conf;	
        },
			
        getForm: function() {
            return form;		
        },
			
        getInputs: function() {
            return inputs;	
        },		
			
        reflow: function() {
            inputs.each(function()  {
                var input = $(this),
                msg = input.data("msg.el");
						 
                if (msg) {						
                    var pos = getPosition(input, msg, conf);
                    msg.css({
                        top: pos.top, 
                        left: pos.left
                    });
                }
            });
            return self;
        },
			
        /* @param e - for internal use only */
        invalidate: function(errs, e) {
				
            // errors are given manually: { fieldName1: 'message1', fieldName2: 'message2' }
            if (!e) {
                var errors = [];
                $.each(errs, function(key, val) {
                    var input = inputs.filter("[name='" + key + "']");
                    if (input.length) {
							
                        // trigger HTML5 ininvalid event
                        input.trigger("OI", [val]);
							
                        errors.push({
                            input: input, 
                            messages: [val]
                            });				
                    }
                });

                errs = errors; 
                e = $.Event();
            }
				
            // onFail callback
            e.type = "onFail";					
            fire.trigger(e, [errs]); 
				
            // call the effect
            if (!e.isDefaultPrevented()) {						
                effects[conf.effect][0].call(self, errs, e);													
            }
				
            return self;
        },
			
        reset: function(els) {
            els = els || inputs;
            els.removeClass(conf.errorClass).each(function()  {
                var msg = $(this).data("msg.el");
                if (msg) {
                    msg.remove();
                    $(this).data("msg.el", null);
                }
            }).unbind(conf.errorInputEvent || '');
            return self;
        },
			
        destroy: function() { 
            form.unbind(conf.formEvent + ".V").unbind("reset.V"); 
            inputs.unbind(conf.inputEvent + ".V").unbind("change.V");
            return self.reset();	
        }, 
			
			
        //{{{  checkValidity() - flesh and bone of this tool
						
        /* @returns boolean */
        checkValidity: function(els, e) {
				
            els = els || inputs;    
            els = els.not(":disabled");
            if (!els.length) {
                return true;
            }

            e = e || $.Event();

            // onBeforeValidate
            e.type = "onBeforeValidate";
            fire.trigger(e, [els]);				
            if (e.isDefaultPrevented()) {
                return e.result;
            }				
					
            // container for errors
            var errs = [];
 
            // loop trough the inputs
            els.not(":radio:not(:checked)").each(function() {
						
                // field and it's error message container						
                var msgs = [], 
                el = $(this).data("messages", msgs),
                event = dateInput && el.is(":date") ? "onHide.v" : conf.errorInputEvent + ".v";					
					
                // cleanup previous validation event
                el.unbind(event);
					
					
                // loop all validator functions
                $.each(fns, function() {
                    var fn = this, match = fn[0]; 
					
                    // match found
                    if (el.filter(match).length)  {  
							
                        // execute a validator function
                        var returnValue = fn[1].call(self, el, el.val());
							
							
                        // validation failed. multiple substitutions can be returned with an array
                        if (returnValue !== true) {								
								
                            // onBeforeFail
                            e.type = "onBeforeFail";
                            fire.trigger(e, [el, match]);
                            if (e.isDefaultPrevented()) {
                                return false;
                            }
								
                            // overridden custom message
                            var msg = el.attr(conf.messageAttr);
                            if (msg) { 
                                msgs = [msg];
                                return false;
                            } else {
                                pushMessage(msgs, match, returnValue);
                            }
                        }							
                    }
                });
					
                if (msgs.length) { 
						
                    errs.push({
                        input: el, 
                        messages: msgs
                    });  
						
                    // trigger HTML5 ininvalid event
                    el.trigger("OI", [msgs]);
						
                    // begin validating upon error event type (such as keyup) 
                    if (conf.errorInputEvent) {							
                        el.bind(event, function(e) {
                            self.checkValidity(el, e);		
                        });							
                    } 					
                }
					
                if (conf.singleError && errs.length) {
                    return false;
                }
					
            });
				
				
            // validation done. now check that we have a proper effect at hand
            var eff = effects[conf.effect];
            if (!eff) {
                throw "Validator: cannot find effect \"" + conf.effect + "\"";
            }
				
            // errors found
            if (errs.length) {					 
                self.invalidate(errs, e); 
                return false;
					
            // no errors
            } else {
					
                // call the effect
                eff[1].call(self, els, e);
					
                // onSuccess callback
                e.type = "onSuccess";					
                fire.trigger(e, [els]);
					
                els.unbind(conf.errorInputEvent + ".v");
            }
				
            return true;				
        }
    //}}} 
			
    });
		
    // callbacks	
    $.each("onBeforeValidate,onBeforeFail,onFail,onSuccess".split(","), function(i, name) {
				
        // configuration
        if ($.isFunction(conf[name]))  {
            $(self).bind(name, conf[name]);	
        }
			
        // API methods				
        self[name] = function(fn) {
            if (fn) {
                $(self).bind(name, fn);
            }
            return self;
        };
    });	
		
		
    // form validation
    if (conf.formEvent) {
        form.bind(conf.formEvent + ".V", function(e) {
            if (!self.checkValidity(null, e)) { 
                return e.preventDefault(); 
            }
            // Reset event type and target
            e.target = form;
            e.type = conf.formEvent;
        });
    }
		
    // form reset
    form.bind("reset.V", function()  {
        self.reset();			
    });
		
    // disable browser's default validation mechanism
    if (inputs[0] && inputs[0].validity) {
        inputs.each(function()  {
            this.oninvalid = function() { 
                return false; 
            };		
        });
    }
		
    // Web Forms 2.0 compatibility
    if (form[0]) {
        form[0].checkValidity = self.checkValidity;
    }
		
    // input validation               
    if (conf.inputEvent) {
        inputs.bind(conf.inputEvent + ".V", function(e) {
            self.checkValidity($(this), e);
        });	
    } 
	
    // checkboxes, selects and radios are checked separately
    inputs.filter(":checkbox, select").filter("[required]").bind("change.V", function(e) {
        var el = $(this);
        if (this.checked || (el.is("select") && $(this).val())) {
            effects[conf.effect][1].call(self, el, e); 
        }
    });		
		
    var radios = inputs.filter(":radio").change(function(e) {
        self.checkValidity(radios, e);
    });
		
    // reposition tooltips when window is resized
    $(window).resize(function() {
        self.reflow();		
    });
}

	
// jQuery plugin initialization
$.fn.validator = function(conf) { 
		
    var instance = this.data("validator");
		
    // destroy existing instance
    if (instance) { 
        instance.destroy();
        this.removeData("validator");
    } 
		
    // configuration
    conf = $.extend(true, {}, v.conf, conf);		
		
    // selector is a form		
    if (this.is("form")) {
        return this.each(function() {			
            var form = $(this); 
            instance = new Validator(form.find(":input"), form, conf);	 
            form.data("validator", instance);
        });
			
    } else {
        instance = new Validator(this, this.eq(0).closest("form"), conf);
        return this.data("validator", instance);
    }     
		
};   
		
})(jQuery);
Do(function(){
    $.tools.validator.conf.lang = "zh-cn";
    $.tools.validator.fn("[data-equals]", "Different value and $ 1", function(input) {
       var name = input.attr("data-equals"),
          field = this.getInputs().filter("[name=" + name + "]"); 
       return input.val() == field.val() ? true : [name]; 
    });
    $.tools.validator.fn("[type=time]", "Please supply a valid time", function(input, value) { 
       return /^\d\d:\d\d$/.test(value);
    });
    $.tools.validator.fn("[name=select]", "Please select a value", function(input, value) { 
       if( value == "0"){
           return false;
       }else{
           return true;
       }
    });
    $.tools.validator.localize("zh-cn",{
        "*":"输入不合法",
        ":email":"邮件地址不正确",
        ":number":"请输入数字",
        ":url":"不是合法的URL",
        "[max]":"输入值不能大于 $1",
        "[min]":"输入值不能小于$1",
        "[required]":"必填",
        "[data-equals]":"两次输入不一致，请确认",
        "[type=time]":"请输入正确的时间(如: 24:00:00)"
    });
});